#if 1
#include "widget.h"
#include <QApplication>
#include <QSettings>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QSettings::setDefaultFormat(QSettings::IniFormat);
    a.setApplicationName("vtk.demo");
    a.setOrganizationDomain("www.vtk.org");
    Widget w;
    w.show();

    return a.exec();
}
#else
#include <QApplication>
#include <iostream>
#include <string>
#include <cstdlib>
#include <chrono>

#include <vtkVersion.h>
#include <vtkSmartPointer.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkPolyDataToImageStencil.h>
#include <vtkPointData.h>
#include <vtkImageConstantPad.h>
#include <vtkTransformFilter.h>
#include <vtkTransform.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkActor.h>
#include <vtkProperty.h>
#include <vtkSTLReader.h>
#include <vtkPoints.h>
#include <vtkGlyph3DMapper.h>
#include <vtkCellArray.h>
#include <vtkTransformPolyDataFilter.h>
#include <vtkStripper.h>
#include <vtkPolyDataNormals.h>
#include <vtkTriangleFilter.h>
#include <vtkCubeSource.h>
#include <vtkImageData.h>
#include <vtkImageStencilToImage.h>
#include <vtkInteractorStyle.h>
#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkInteractionStyle);
/**
 * This program reads a STL mesh and converts it into a volume
 * representation (vtkImageData) where the foreground voxels are 1
 * and the background voxels are 0. The resulting image is visualized via
 * vtk.
 **/


// function taken from 3d slicer and modified. original version:
// https://github.com/Slicer/Slicer/blob/master/Libs/vtkSegmentationCore/vtkClosedSurfaceToBinaryLabelmapConversionRule.cxx#L118

bool ConvertPolyDataToBinaryLabelMap(vtkPolyData* closedSurfacePolyData, vtkImageData* binaryLabelMap)
{
  if (closedSurfacePolyData->GetNumberOfPoints() < 2 || closedSurfacePolyData->GetNumberOfCells() < 2)
  {
    std::cout << "Convert: Cannot create binary labelmap from surface with number of points: "
        << closedSurfacePolyData->GetNumberOfPoints() << " and number of cells: "
        << closedSurfacePolyData->GetNumberOfCells() << std::endl;

      return false;
  }


  // Allocate output image data
  binaryLabelMap->AllocateScalars(VTK_UNSIGNED_CHAR, 1);


  void* binaryLabelMapVoxelsPointer = binaryLabelMap->GetScalarPointerForExtent(binaryLabelMap->GetExtent());

  if (!binaryLabelMapVoxelsPointer)
  {
    std::cout << "Convert: Failed to allocate memory for output labelmap image!" << std::endl;

    return false;
  }
  else
  {
    // Set voxel values to 0
    int extent[6] = { 0, -1,
                      0, -1,
                      0, -1 };

    binaryLabelMap->GetExtent(extent);
    memset(binaryLabelMapVoxelsPointer, 0, ((extent[1] - extent[0] + 1)
                                          * (extent[3] - extent[2] + 1)
                                          * (extent[5] - extent[4] + 1)
                                          * binaryLabelMap->GetScalarSize()
                                          * binaryLabelMap->GetNumberOfScalarComponents()));
  }

  // Compute polydata normals
  vtkNew<vtkPolyDataNormals> normalFilter;
  normalFilter->SetInputData(closedSurfacePolyData);
  normalFilter->ConsistencyOn();

  // Make sure that we have a clean triangle polydata
  vtkNew<vtkTriangleFilter> triangle;
  triangle->SetInputConnection(normalFilter->GetOutputPort());

  // Convert to triangle strip
  vtkNew<vtkStripper> stripper;
  stripper->SetInputConnection(triangle->GetOutputPort());

  // Convert polydata to stencil
  vtkNew<vtkPolyDataToImageStencil> polyDataToImageStencil;
  polyDataToImageStencil->SetInputConnection(stripper->GetOutputPort());
  polyDataToImageStencil->SetOutputSpacing(binaryLabelMap->GetSpacing());
  polyDataToImageStencil->SetOutputOrigin(binaryLabelMap->GetOrigin());
  polyDataToImageStencil->SetOutputWholeExtent(binaryLabelMap->GetExtent());
  polyDataToImageStencil->Update();

  // Convert stencil to image
  vtkNew<vtkImageStencilToImage> imageStencilToImage;
  imageStencilToImage->SetInputConnection(polyDataToImageStencil->GetOutputPort());
  imageStencilToImage->SetOutsideValue(0);
  imageStencilToImage->SetInsideValue(1);
  imageStencilToImage->Update();

  // Save result to output
  binaryLabelMap->DeepCopy(imageStencilToImage->GetOutput());

  return true;
}



int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    // constants

    static constexpr unsigned char outval = 0;
    float step=0.75;
    QString name="test.stl";
    // load STL file
    vtkNew<vtkSTLReader> reader;
    reader->SetFileName(name.toStdString().data());
    reader->Update();

    vtkNew<vtkPolyData> pd;
    pd->DeepCopy(reader->GetOutput());

    // compute bounds for stl mesh polydata
    double bounds[6];
    pd->GetBounds(bounds);


    int imageResolutionX = (bounds[1]-bounds[0])/step;
    int imageResolutionY = (bounds[3]-bounds[2])/step;
    int imageResolutionZ = (bounds[5]-bounds[4])/step;
    // vtkImageData for voxel representation storage
    vtkNew<vtkImageData> voxelImage;

    // Specify the size of the image data
    voxelImage->SetDimensions( imageResolutionX,
                               imageResolutionY,
                               imageResolutionZ);

    double spacing[3];                      // desired volume spacing
    spacing[0] = 1.0 / imageResolutionX;
    spacing[1] = 1.0 / imageResolutionY;
    spacing[2] = 1.0 / imageResolutionZ;

    voxelImage->SetSpacing(spacing);
    voxelImage->SetExtent(0, imageResolutionX - 1,
                          0, imageResolutionY - 1,
                          0, imageResolutionZ - 1);

    double origin[3];
    origin[0] = bounds[0] + spacing[0] / 2;
    origin[1] = bounds[2] + spacing[1] / 2;
    origin[2] = bounds[4] + spacing[2] / 2;
    voxelImage->SetOrigin(origin);
    voxelImage->AllocateScalars(VTK_UNSIGNED_CHAR, 1);


    // fill the image with background voxels:
    voxelImage->GetPointData()->GetScalars()->Fill(outval);


    // start measure time
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();


    // convert to voxelimage
    ConvertPolyDataToBinaryLabelMap(pd, voxelImage);


    // end measure time
    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();
    cout << "duration: " << static_cast<double>(duration) / 1000000.0 << std::endl;


    /////////////////////////////////////////////////////////////////
    // Visualization
    /////////////////////////////////////////////////////////////////


    // create point data for visualization via vtkGlyph3DMapper
    // based on the example code from
    // https://www.vtk.org/Wiki/VTK/Examples/Cxx/Visualization/Glyph3DMapper
    vtkNew<vtkPoints> points;

    unsigned char* voxelImageData = static_cast<unsigned char*>(voxelImage->GetScalarPointer());

    for (int x = 0; x < imageResolutionX; x++)
    {
      for (int y = 0; y < imageResolutionY; y++)
      {
        for (int z = 0; z < imageResolutionZ; z++)
        {
          unsigned char pixelValue = voxelImageData[x + imageResolutionX * (y + imageResolutionY * z)];

          if(pixelValue != outval)
            points->InsertNextPoint(x, y, z);
        }
      }
    }

    vtkNew<vtkPolyData> polydata;
    polydata->SetPoints(points);

    vtkNew<vtkPolyData> glyph;

    vtkNew<vtkCubeSource> cubeSource;
    cubeSource->Update();
    vtkNew<vtkGlyph3DMapper> glyph3Dmapper;
    glyph3Dmapper->SetSourceConnection(cubeSource->GetOutputPort());
    glyph3Dmapper->SetInputData(polydata);
    glyph3Dmapper->Update();


    vtkNew<vtkActor> actor;
    actor->SetMapper(glyph3Dmapper);
    actor->GetProperty()->SetColor(0.0, 1.0, 0.0);
    //actor->GetProperty()->SetOpacity(0.25); // uncomment for transparency


    vtkNew<vtkRenderer> renderer;
    renderer->UseFXAAOn();

    vtkNew<vtkRenderWindow> renderWindow;
    renderWindow->AddRenderer(renderer);

    vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
    renderWindowInteractor->SetRenderWindow(renderWindow);

    renderer->AddActor(actor);
    renderer->SetBackground(1.0, 1.0, 1.0);

    renderWindow->Render();
    renderWindowInteractor->Start();
    return a.exec();
}
#endif
